#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
基础应用分析器类
所有平台特定分析器的基类
"""

import os
import re
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Dict, List, Any, Optional, Union

from src.utils.logger import get_logger
from src.models.result import AnalysisResult, FrameworkInfo, AppInfo

class BaseAnalyzer(ABC):
    """应用分析器基类"""
    
    def __init__(
        self, 
        app_path: Path, 
        config: Optional[Dict[str, Any]] = None,
        verbose: bool = False
    ):
        """
        初始化分析器
        
        Args:
            app_path: 应用程序路径
            config: 配置字典
            verbose: 是否输出详细日志
        """
        self.app_path = app_path
        self.config = config or {}
        self.verbose = verbose
        self.logger = get_logger(self.__class__.__name__)
        self.signatures = self.config.get("signatures", [])
        self.file_extensions = self.config.get("file_extensions", [])
        
        self.logger.info(f"初始化{self.__class__.__name__}，目标路径: {app_path}")
    
    @abstractmethod
    def analyze(self) -> AnalysisResult:
        """
        执行应用分析
        
        Returns:
            分析结果对象
        """
        pass
    
    @abstractmethod
    def detect_app_info(self) -> AppInfo:
        """
        检测应用基本信息
        
        Returns:
            应用程序信息对象
        """
        pass
    
    def scan_files(self, extensions: Optional[List[str]] = None) -> List[Path]:
        """
        扫描应用目录下的文件
        
        Args:
            extensions: 文件扩展名列表，如果为None则使用配置中的扩展名
            
        Returns:
            符合条件的文件路径列表
        """
        if extensions is None:
            extensions = self.file_extensions
        
        result_files = []
        
        # 如果是文件而非目录，直接返回
        if self.app_path.is_file():
            if not extensions or any(self.app_path.name.endswith(ext) for ext in extensions):
                return [self.app_path]
        
        # 遍历目录
        for root, _, files in os.walk(self.app_path):
            root_path = Path(root)
            for file in files:
                if not extensions or any(file.endswith(ext) for ext in extensions):
                    result_files.append(root_path / file)
        
        self.logger.debug(f"扫描到 {len(result_files)} 个文件")
        return result_files
    
    def search_signatures(
        self, 
        files_or_path: Union[List[Path], Path], 
        file_patterns: Optional[List[str]] = None, 
        search_patterns: Optional[List[str]] = None
    ) -> List[str]:
        """
        搜索文件中的签名特征
        
        Args:
            files_or_path: 要搜索的文件列表或单个文件路径
            file_patterns: 文件名模式列表，用于匹配文件名
            search_patterns: 搜索模式列表，用于匹配文件内容
            
        Returns:
            匹配的文件路径列表或找到的特征签名列表
        """
        # 兼容旧接口: 如果传入单个文件路径，且没有提供file_patterns
        if isinstance(files_or_path, Path) and file_patterns is None:
            # 旧接口行为: 在单个文件中搜索signatures
            signatures = search_patterns
            if signatures is None:
                signatures = self.signatures
            
            if not signatures:
                return []
            
            found_signatures = []
            try:
                # 对于文本文件
                if self._is_text_file(files_or_path):
                    with open(files_or_path, 'r', encoding='utf-8', errors='ignore') as f:
                        content = f.read()
                        for sig in signatures:
                            if sig.lower() in content.lower():
                                found_signatures.append(sig)
                # 对于二进制文件
                else:
                    with open(files_or_path, 'rb') as f:
                        content = f.read()
                        for sig in signatures:
                            if sig.encode() in content:
                                found_signatures.append(sig)
            except Exception as e:
                self.logger.debug(f"搜索特征签名时出错: {e}")
            
            return found_signatures
        
        # 新接口: 在文件列表中搜索模式
        if (file_patterns is None or len(file_patterns) == 0) and (search_patterns is None or len(search_patterns) == 0):
            return []
            
        matched_files = []
        files = files_or_path if isinstance(files_or_path, list) else [files_or_path]
        
        # 首先根据文件名匹配
        if file_patterns:
            for file_path in files:
                file_str = str(file_path)
                for pattern in file_patterns:
                    if re.search(pattern, file_str, re.IGNORECASE):
                        matched_files.append(file_str)
                        break

        # 然后搜索文件内容
        if search_patterns:
            for file_path in files:
                # 如果已经在文件名匹配中找到，跳过内容搜索
                if str(file_path) in matched_files:
                    continue
                    
                try:
                    # 判断是否为文本文件
                    if self._is_text_file(file_path):
                        with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                            content = f.read()
                            for pattern in search_patterns:
                                if re.search(pattern, content, re.IGNORECASE):
                                    matched_files.append(str(file_path))
                                    break
                    # 二进制文件
                    else:
                        with open(file_path, 'rb') as f:
                            content = f.read()
                            for pattern in search_patterns:
                                try:
                                    pattern_bytes = pattern.encode('utf-8', errors='ignore')
                                    if pattern_bytes in content:
                                        matched_files.append(str(file_path))
                                        break
                                except Exception:
                                    # 如果模式无法编码为字节，跳过
                                    continue
                except Exception as e:
                    self.logger.debug(f"搜索文件 {file_path} 时出错: {e}")
        
        return matched_files
    
    def _is_text_file(self, file_path: Path) -> bool:
        """
        检查文件是否为文本文件
        
        Args:
            file_path: 文件路径
            
        Returns:
            如果是文本文件则返回True，否则返回False
        """
        # 常见文本文件扩展名
        text_extensions = [
            '.txt', '.xml', '.json', '.yml', '.yaml', '.html', '.htm',
            '.js', '.py', '.java', '.kt', '.swift', '.c', '.cpp', '.h',
            '.cs', '.go', '.rb', '.php', '.sh', '.bat', '.ps1', '.md',
            '.css', '.ts', '.gradle', '.properties', '.plist'
        ]
        
        # 根据扩展名判断
        if any(file_path.name.endswith(ext) for ext in text_extensions):
            return True
        
        # 尝试读取文件内容判断
        try:
            with open(file_path, 'rb') as f:
                chunk = f.read(1024)
                return b'\0' not in chunk  # 二进制文件通常包含空字节
        except Exception:
            return False 
